package ws

import (
	"encoding/json"
	"fmt"
	"net/url"
	"sync"
	"sync/atomic"
	"time"

	"github.com/gorilla/websocket"
	"starsine.cn/appTpl/cmn"
	"starsine.cn/appTpl/cmn/config/aconf"
	. "starsine.cn/appTpl/cmn/defs"
	"starsine.cn/appTpl/cmn/logs"
	. "starsine.cn/appTpl/svccenter/registry/restful/openapi"
	"starsine.cn/appTpl/svcregistry/restful/token"
)

type WsClient struct {
	url        url.URL
	conn       *websocket.Conn
	done       chan struct{}
	msgQue     chan []byte
	isConnect  atomic.Bool
	msgHandler func(msg []byte)
	sync.RWMutex
	wsWirteMutex sync.Mutex

	appCtxt *AppContext
}

func CreateWsClient(appCtxt *AppContext) *WsClient {
	p := new(WsClient)

	p.conn = nil
	p.msgQue = make(chan []byte, 10000)
	p.isConnect.Store(false)
	p.msgHandler = nil
	p.appCtxt = appCtxt

	addr := aconf.GetString("SvcCenter.Addr", "0.0.0.0:20000", aconf.WithENV("sc_addr"))
	path := "/v1/ws"
	p.url = url.URL{Scheme: "ws", Host: addr, Path: path}

	return p
}

func (p *WsClient) Start() {
	go p.msgHandle()
	go p.sm()
}

func (p *WsClient) connect2Ws() error {
	p.RWMutex.Lock()
	defer p.RWMutex.Unlock()

	tkn, valid := token.GetToken()
	if !valid {
		logs.Debugf("token is not valid for connect")
		cmn.GetEvntEmiter().Emit("bad-token")
		return fmt.Errorf("bad-token: %s", tkn)
	}

	c, r, err := websocket.DefaultDialer.Dial(p.url.String(), map[string][]string{
		"Content-Type": {"application/json;charset=utf-8"},
		"X-Auth-Token": {tkn},
	})
	if err != nil {
		if r != nil && r.Body != nil {
			body := WsConnResponse{}
			d := json.NewDecoder(r.Body)
			if e := d.Decode(&body); e != nil {
				logs.Errorf("connect2WS decode dial error info fail: %s", e)
				goto _dialFail
			}
			if body.MsgHead.Message == "bad token" {
				cmn.GetEvntEmiter().Emit("bad-token")
			}
		}
	_dialFail:
		logs.Errorf("connect2WS to dial fail: %s", err)
		return fmt.Errorf("connect2Ws to dial fail: %s", err)
	}

	p.conn = c
	p.done = make(chan struct{})
	go p.readProc(p.done)

	logs.Debugf("WsClient connected")

	return nil
}

func (p *WsClient) connectClose() {
	p.RWMutex.Lock()
	defer p.RWMutex.Unlock()

	if p.conn != nil {
		p.conn.Close()
		p.conn = nil
	}
}

func (p *WsClient) readProc(done chan struct{}) {
	for {
		_, msg, err := p.conn.ReadMessage()
		if err != nil {
			logs.Errorf("wsclient ReadMessage fail: %s", err)
			close(p.done)
			return
		}

		p.msgQue <- msg
		// logs.Debugf("wsclient recv: %s", msg)
	}
}

func (p *WsClient) msgHandle() {
	logs.Infof("wsclient msgHandle routine start")
	p.appCtxt.Wg.Add(1)
	defer func() {
		logs.Warnf("wsclient msgHandle routine exit")
		p.appCtxt.Wg.Done()
	}()

	for {
		select {
		case msg := <-p.msgQue:
			if p.msgHandler != nil {
				p.msgHandler(msg)
			}
			// logs.Debugf("Get msg: %v", msg)
		case <-p.appCtxt.Ctx.Done():
			logs.Warnf("wsclient msgHandle routine ctxt done to quit")
			return
		}
	}
}

func (p *WsClient) sm() {
	logs.Infof("wsclient sm routine start")
	p.appCtxt.Wg.Add(1)
	defer func() {
		logs.Warnf("wsclient sm routine exit")
		p.appCtxt.Wg.Done()
	}()

	ticker := time.NewTicker(time.Second)
	defer ticker.Stop()
	for {
		select {
		case <-p.done:
			p.isConnect.Store(false)
			p.connectClose()
		case <-ticker.C:
			if !p.isConnect.Load() {
				if err := p.connect2Ws(); err != nil {
					logs.Debugf("WsClient to connect2Ws fail, retry after 1 second")
					continue
				}
				p.isConnect.Store(true)
			}
		case <-p.appCtxt.Ctx.Done():
			logs.Warnf("wsclient ctxt done to quit")
			if p.conn != nil {
				p.wsWirteMutex.Lock()
				err := p.conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
				p.wsWirteMutex.Unlock()
				if err != nil {
					logs.Errorf("wsclient WriteMessage(websocket.CloseMessage) fail: ", err)
				}
				p.conn.Close()
				p.conn = nil
			}

			return
		}
	}
}

func (p *WsClient) writeMessage(msg []byte) error {
	p.RWMutex.RLock()
	defer p.RWMutex.RUnlock()

	if !p.isConnect.Load() {
		logs.Warnf("wsclient not connected")
		return fmt.Errorf("wsclient not connected")
	}

	p.wsWirteMutex.Lock()
	err := p.conn.WriteMessage(websocket.TextMessage, msg)
	p.wsWirteMutex.Unlock()
	if err != nil {
		logs.Warnf("wsclient WriteMessage fail: %s", err)
		// close(p.done)
	}
	return err
}

func (p *WsClient) registerProc(fn func(msg []byte)) {
	p.RWMutex.RLock()
	defer p.RWMutex.RUnlock()

	p.msgHandler = fn
}
